---
title: 代理模式
description: 在不改变原始类（或叫被代理类）的情况下，通过引入代理类来给原始类附加功能。
---

## 原理解析
Proxy Design Pattern 在不改变原始类（被代理类） 代码的情况下，通过引入代理类来给原始类附加功能。


参照基于接口而非实现编程的设计思想，将原始类对象替换为代理类对象的时候，为了让代码改动尽量少，代理类和原始类需要实现相同的接口。但是如果原始类并没有定义接口，并且原始类代码并不是我们开发维护的（比如来自三方类库），没有办法直接修改原始类，给它定义一个接口。

对于外部类的扩展，一般采用继承的方式，让代理类继承原始类，然后扩展附加功能。

## 动态代理
静态代理需要为每一个原始类创建一个代理类，并且每个代理类中的代码都是模板式的重复代码。

可以使用动态代理来解决这个问题。所谓动态代理（Dynamic Proxy） ,就是我们不事先为每个原始类编写代理类，而是在运行的时候，动态地创建原始类对应的代理类，然后在系统中用代理类替换到原始类。


:::tip
Java 语言本身已经提供了动态代理的语法（动态代理底层依赖的就是 java 的反射语法）。
:::


Spring AOP 底层的实现就是基于动态代理。用户配置好那些类需要代理，并定义好在执行原始类的业务代码前后执行那些附加功能。Spring 为这些类创建动态代理对象，并在 JVM 中替代原始类对象。

## 应用场景
### 业务系统的非功能性需求开发
代理模式最常用的一个应用场景就是，在业务系统中开发一些非功能性需求，比如：监控、统计、鉴权、限流、事务、幂等、日志等。 将这些附加功能与业务功能解耦，放到代理类中统一处理，让程序员只需关注业务方面的开发。

### 代理模式在 RPC、缓存中的应用
实际上 RPC 也可以看作是一种代理模式，GoF 的《设计模式》一书中把它称为远程代理。通过远程代理，将网络通信、数据编码等细节隐藏起来。客户端在使用 RPC 服务的时候，就像使用本地函数一样，无需了解跟服务器交互的细节。

关于代理模式在缓存中的应用。假设要开发一个接口请求的缓存功能，对于某些接口请求，如果入参相同，在设定的过期时间内，直接返回缓存结果，而不用重新进行业务处理。比如，针对获取用户个人信息的需求，我们可以开发两个接口，一个支持缓存，一个支持实时查询。对于需要实时数据的需求，我们让其调用实时查询接口，对于不需要实时数据的需求，我们让其调用支持缓存的接口。
但是这样显然增加了开发成本，并且让代码看起来很臃肿，也不方便缓存的集中管理、集中配置。

针对这些问题，代理模式就能排上用场，确切地说，应该是动态代理。如果是基于 Spring 框架开发的话，那就可以直接在 AOP 切面中完成接口缓存的功能。在应用启动时，从配置文件中加载需要支持缓存的接口，以及相应的缓存策略（比如过期时间）等。当请求到来的时候，我们在 AOP 切面中拦截请求，如果请求中带有支持缓存的字段（比如 http://…?..&cached=true），我们便从缓存（内存缓存或者 Redis 缓存等）中获取数据直接返回。


## 总结
### 代理模式的原理和实现
在不改变原始类（或叫被代理类）的情况下，通过引入代理类来给原始类附加功能。一般情况下，我们让代理类和原始类实现同样的接口。但是，如果原始类并没有定义接口，并且原始类并不是我们开发维护的。在这种情况下，我们可以通过让代理类继承原始类的的方法来实现代理模式。

实现方式有两种，一种是基于组合、一种基于继承。
### 动态代理的原理和实现
静态代理需要针对每个类都创建一个代理类，并且每个代理类中的代码都有点像模板式的“重复代码”增加了维护成本和开发成本。对于静态代理存在的问题，我们可以通过动态代理来解决。我们不事先为每个原始类编写代理类，而是在运行的时候动态地创建原始类对应的代理类，然后在系统中用代理类替换掉原始类。
### 代理模式的应用场景
代理模式常用在业务系统开发中的一些非共功能性需求，比如：监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能和业务功能解耦，放到代理类中统一处理，让程序员只关注业务方面的开发。除此之外，代理模式还可以用在 RPC、缓存等应用场景中。

